import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QButtonGroup,
                             QLineEdit, QLabel, QDialog, QSizePolicy,QCompleter,QMessageBox,QApplication)
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
import pandas as pd
import importlib.util


from ui.main import Ui_ui_test  # 从生成的UI文件导入
import csv
import FFT
import code_to_mvolt


def load_external_file(file_name):
    """
    动态加载外部文件（可执行文件所在目录下的file_name）
    :param file_name: 要加载的外部文件名（如config.py）
    :return: 加载后的模块对象（可像正常import一样使用）
    """
    # 1. 获取可执行文件所在目录（关键：兼容打包后和开发环境）
    if getattr(sys, 'frozen', False):
        # 打包后：sys.frozen为True，_MEIPASS是临时解压目录，实际exe所在目录是sys.executable的父目录
        exe_dir = os.path.dirname(sys.executable)
    else:
        # 开发环境：直接用当前脚本所在目录
        exe_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 2. 拼接外部文件的绝对路径（确保用户将file_name放在exe同目录）
    file_path = os.path.join(exe_dir, file_name)
    
    # 3. 检查文件是否存在（不存在则报错，提示用户放置文件）
    if not os.path.exists(file_path):
        raise FileNotFoundError(
            f"未找到外部文件：{file_name}\n"
            f"请将{file_name}放在可执行文件所在目录：{exe_dir}"
        )
    
    # 4. 动态加载文件（importlib实现，Python3.5+支持）
    spec = importlib.util.spec_from_file_location(
        name=file_name[:-3],  # 模块名（去掉.py后缀）
        location=file_path    # 外部文件路径
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)  # 执行模块，加载最新内容
    
    print(f"✅ 成功加载外部文件：{file_path}（修改后重启程序即可生效）")
    return module

def enable_drag_drop(line_edit: QLineEdit):
    """
    使给定的 QLineEdit 具有拖放功能。
    """
    def dragEnterEvent(event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if urls:
                # 获取第一个文件/文件夹的路径
                path = urls[0].toLocalFile()
                line_edit.setText(path)
            event.acceptProposedAction()
        else:
            event.ignore()

    # 设置允许拖放事件
    line_edit.setAcceptDrops(True)

    # 重写拖放事件处理方法
    line_edit.dragEnterEvent = dragEnterEvent
    line_edit.dragMoveEvent = dragMoveEvent
    line_edit.dropEvent = dropEvent

class WaveformWindow(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("波形图")
        self.setMinimumSize(1000, 600)  # 增大默认窗口尺寸，查看更清晰

        # 1. 创建 matplotlib 核心组件
        self.figure = Figure(figsize=(12, 7), dpi=100)
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)  # 新增：查看工具栏

        # 2. 中文字体配置
        # plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows
        plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # Mac
        plt.rcParams['axes.unicode_minus'] = False

        # 4. 窗口布局（工具栏 + 画布 + 保存按钮）
        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)  # 工具栏在顶部
        layout.addWidget(self.canvas, stretch=1)  # 画布占满中间（拉伸填充）
        # 保存按钮靠右对齐
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def plot(self, voltage_data, start_idx, end_idx):
        """绘制波形（保持原有逻辑，优化视图）"""
        self.figure.clear()
        ax = self.figure.add_subplot(111)

        # 绘制电压波形（优化线条颜色和透明度）
        x = range(start_idx, end_idx + 1)
        ax.plot(x, voltage_data, color='#1f77b4', linewidth=0.8, alpha=0.9, label='电压波形')

        # 图表样式优化（更清晰的标签和网格）
        ax.set_xlabel('数据点序号（1开始）', fontsize=13, fontweight='bold')
        ax.set_ylabel('电压值 (V)', fontsize=13, fontweight='bold')
        ax.set_title(
            f'电压波形图（第 {start_idx} - 第 {end_idx} 个数据点）', 
            fontsize=15, fontweight='bold', pad=25
        )
        ax.legend(loc='upper right', fontsize=11)
        ax.grid(True, alpha=0.4, linestyle='-', linewidth=0.6)
        ax.set_xlim(start_idx, end_idx)  # 贴合数据区间
        ax.tick_params(labelsize=10)  # 调整坐标轴刻度字体大小

        self.canvas.draw()

    def plot_spectrum(self, fundamental_volt_dict, flag_frep):
        """绘制频谱图（接收 fundamental_volt_dict 数据和目标频率 flag_frep）"""
        # 1. 提取数据（频率 + 对应的 dBm 值）
        frequencies = sorted(fundamental_volt_dict.keys())  # 频率排序，确保波形连续
        dbm_values = [fundamental_volt_dict[freq]["dbm"] for freq in frequencies]
        voltage_values = [fundamental_volt_dict[freq]["volt"] for freq in frequencies]

        if not frequencies or not dbm_values:
            QMessageBox.warning(self, "数据错误", "无有效频谱数据可绘制！")
            return

        # 2. 清空画布，绘制频谱
        self.figure.clear()
        ax = self.figure.add_subplot(111)

        # 绘制频谱曲线（蓝色实线，优化视觉效果）
        ax.plot(frequencies, dbm_values, color='#2E86AB', linewidth=1.0, alpha=0.9, label='频谱曲线')

        # 3. 判断是否标记目标频率 flag_frep
        if flag_frep in fundamental_volt_dict:
            # 标记目标频率
            target_dbm = fundamental_volt_dict[flag_frep]["dbm"]
            target_volt = fundamental_volt_dict[flag_frep]["volt"]
            annotate_text = f'目标频率\n频率: {flag_frep} Hz\n幅值: {target_volt:.3f} V\n{target_dbm:.1f} dBm'
            annotate_color = 'orange'  # 目标频率用橙色标注
        else:
            # 无目标频率，标记峰值
            peak_idx = np.argmax(dbm_values)
            flag_frep = frequencies[peak_idx]
            target_dbm = dbm_values[peak_idx]
            target_volt = voltage_values[peak_idx]
            annotate_text = f'峰值\n频率: {flag_frep} Hz\n幅值: {target_volt:.3f} V\n{target_dbm:.1f} dBm'
            annotate_color = 'red'  # 峰值用红色标注

        # 统一标注逻辑（箭头 + 文本框）
        ax.annotate(
            annotate_text,
            xy=(flag_frep, target_dbm),
            xytext=(flag_frep + (max(frequencies)-min(frequencies))*0.05, target_dbm + 3),  # 偏移位置自适应
            arrowprops=dict(facecolor=annotate_color, shrink=0.05, width=1.5, alpha=0.8),
            bbox=dict(boxstyle='round,pad=0.6', fc='yellow', alpha=0.8),
            fontsize=10,
            ha='left'
        )

        # 4. 图表样式优化
        ax.set_xlabel('频率 (Hz)', fontsize=13, fontweight='bold')
        ax.set_ylabel('功率幅值 (dBm)', fontsize=13, fontweight='bold')
        ax.set_title('频谱分析图', fontsize=15, fontweight='bold', pad=25)
        ax.legend(loc='upper right', fontsize=11)
        ax.grid(True, alpha=0.4, linestyle='-', linewidth=0.6)
        ax.set_xlim(min(frequencies), max(frequencies))  # x轴贴合频率范围
        ax.tick_params(labelsize=10)  # 调整刻度字体大小

        # 5. 刷新画布显示
        self.canvas.draw()

class aquilaWindow(QWidget, Ui_ui_test):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  # 初始化UI

        self.comboBox_frep_1.addItems(["112000", "238000","322000", "406000", "464000", "498000"])
        enable_drag_drop(self.textEdit_binPath)
        enable_drag_drop(self.textEdit_csvPath)

        # -------------------------- 关键：单选框分组+默认选中 --------------------------
        # 1. 创建 QButtonGroup（管理4个单选框，实现互斥）
        self.radio_group = QButtonGroup(self)
        self.radio_group.setExclusive(True)  # 强制互斥（默认也是True，可省略，但显式写更清晰）

        # 2. 将4个单选框添加到组（按你的实际变量名替换）
        self.radio_group.addButton(self.radioButton_window)
        self.radio_group.addButton(self.radioButton_flattop_window)
        self.radio_group.addButton(self.radioButton_hanning_window)
        self.radio_group.addButton(self.radioButton_blackman_harris_window)

        # 3. 设置默认选中项（选一个你需要的默认选项，示例选中第一个）
        self.radioButton_flattop_window.setChecked(True) 

        # -------------------------------------------------------------------------
        self.radio_group_vpp = QButtonGroup(self)
        self.radio_group_vpp.setExclusive(True)

        self.radio_group_vpp.addButton(self.radioButton_vpp_apple)
        self.radio_group_vpp.addButton(self.radioButton_vpp_fixture)

        self.radioButton_vpp_fixture.setChecked(True) 
        # -------------------------------------------------------------------------
        self.radio_group_dbm = QButtonGroup(self)
        self.radio_group_dbm.setExclusive(True)

        self.radio_group_dbm.addButton(self.radioButton_dbm_apple)
        self.radio_group_dbm.addButton(self.radioButton_dbm_fixture)

        self.radioButton_dbm_fixture.setChecked(True) 

        # -------------------------------------------------------------------------

        #解析bin文件
        self.pushButton_bin.clicked.connect(self.analysis_bin)
        #解析bin文件
        self.pushButton_csv.clicked.connect(self.analysis_csv)
        #生成波形图
        self.pushButton_time.clicked.connect(self.plot_voltage_range_waveform)
        #生成DBM波形图
        self.pushButton_frep.clicked.connect(self.spectrum_diagram_waveform)

    def analysis_bin(self):
        bin_path = self.textEdit_binPath.toPlainText()
        if not os.path.exists(bin_path):
            QMessageBox.critical(self, "错误", f"文件不存在：\n")
            return False
    
        if not bin_path.lower().endswith(".bin"):
            QMessageBox.critical(self, "错误", f"文件格式错误！\n请选择 .bin 格式文件")
            return False

        self.textBrowser.append("开始解析 Bin 文件======")
        csv_path = code_to_mvolt.decode_bin_to_csv(bin_path)
        self.textEdit_csvPath.setPlainText(csv_path)
        self.textBrowser.append('<font color="green">[成功]</font> 生成 CSV 文件：{}'.format(csv_path))

    def calculate_frequency(self,csv_path):
        self.textBrowser.append("开始计算频率")
        referVolt=int(self.lineEdit_referVolt.text())
        intervalCount=int(self.lineEdit_intervalCount.text()) 
        _sampleRate = int(self.lineEdit_sample_rate.text())
        raw_data = []
        with open(csv_path, 'r') as csvfile:
            # reader = csv.DictReader(csvfile)
            reader = csv.reader(csvfile)
            for i in reader:
                raw_data.append(i)
        freq,end_collect_index,start_collect_index,period = code_to_mvolt.calculate_frequency(raw_data, referVolt, intervalCount, _sampleRate)
        self.textBrowser.append(f"frequency: {freq}")
        self.textBrowser.append(f"end_collect_index: {end_collect_index}")
        self.textBrowser.append(f"start_collect_index: {start_collect_index}")
        self.textBrowser.append(f"period: {period}")
        self.textBrowser.append(f"计算得到的频率: {freq}")

    def calculate_dbm(self,csv_path):
        self.textBrowser.append("开始计算dbm")
        sample_rate = int(self.lineEdit_sample_rate.text())            # 采样率（Hz，需与原始数据采集时一致，原始代码默认125MHz）

        selected_radio = self.radio_group.checkedButton()
        if selected_radio:
            selected_text = selected_radio.text()
            # 执行对应动作
            if selected_text == "无窗":
                window_type = 0
            elif selected_text == "平定窗":
                window_type = 1
            elif selected_text == "汉宁窗":
                window_type = 2
            elif selected_text == "布莱克曼-哈里斯窗":
                window_type = 3  
        try:                  
            load_impedance = float(self.lineEdit_impedance.text())                # 负载阻抗（Ω，默认50Ω，射频常用）
            cal_constant = float(self.lineEdit_cal_constant.text()) 
            gain = float(self.lineEdit_gain1.text()) 
        except ValueError:
            self.show_message("错误", "负载阻抗或校准常数或gain有误")
            return

        selected_radio_vpp = self.radio_group_vpp.checkedButton().text()
        selected_radio_dbm = self.radio_group_dbm.checkedButton().text()
        if selected_radio_vpp == "apple":
            dbm_value_1,fundamental_voltage = FFT.get_dbm_by_frequency(csv_path, sample_rate, load_impedance, float(self.comboBox_frep_1.currentText()),selected_radio_dbm)
        else:
            raw_voltage = FFT.read_voltage_from_csv(csv_path)
            fft_result = FFT.fft_analysis(raw_voltage, sample_rate, window_type,float(self.comboBox_frep_1.currentText()))
            fundamental_voltage = fft_result["fundamental_voltage"]
            if selected_radio_dbm == "fixture":
                dbm_value_1 = FFT.voltage_to_dbm_Fixture(fundamental_voltage, gain,load_impedance,cal_constant)
            else:
                dbm_value_1 = FFT.voltage_to_dbm_apple(fundamental_voltage, gain,load_impedance,cal_constant)
            print(f"===============================================")
            print(selected_radio_dbm)
            print(f"对应 dBm 值：{dbm_value_1:.2f} dBm")
            print(f"对应 幅值：{fundamental_voltage:.2f} V")
            print(f"===============================================")

        self.textBrowser.append(f"FFT分析结果：")
        self.textBrowser.append(f"gin:{gain:9f}")
        self.textBrowser.append(f"频率电压幅值:{fundamental_voltage:9f} V")
        if dbm_value_1 is not None:
            self.textBrowser.append(f"频率-dbm计算结果（负载阻抗{load_impedance}Ω）：{dbm_value_1:.9f} dBm")

    def analysis_csv(self):
        csv_path = self.textEdit_csvPath.toPlainText()
        if not os.path.exists(csv_path):
            QMessageBox.critical(self, "错误", f"文件不存在：\n")
            return False
    
        if not csv_path.lower().endswith(".csv"):
            QMessageBox.critical(self, "错误", f"文件格式错误！\n请选择 .csv 格式文件")
            return False

        self.textBrowser.append("\n=== 开始解析 csv 文件 ===\n")
        self.calculate_frequency(csv_path)
        self.calculate_dbm(csv_path)

    def plot_voltage_range_waveform(self):
        csv_path = self.textEdit_csvPath.toPlainText()
        if not os.path.exists(csv_path):
            QMessageBox.critical(self, "错误", f"文件不存在：\n")
            return False
    
        if not csv_path.lower().endswith(".csv"):
            QMessageBox.critical(self, "错误", f"文件格式错误！\n请选择 .csv 格式文件")
            return False


        """按钮点击事件：启动子线程处理数据，弹出波形窗口"""
        try:
            start_idx = int(self.lineEdit_start.text())             # 起始数据点（第1）
            end_idx = int(self.lineEdit_end.text())                # 结束数据点（第10000个）
        except ValueError:
            self.show_message("错误", "起始/结束点必须是整数！")
            return

        # 2. 验证输入合法性
        if start_idx < 1 or end_idx < 1:
            QMessageBox.warning(self, "输入错误", "起始点/结束点不能小于1！")
            return
        if start_idx >= end_idx:
            QMessageBox.warning(self, "输入错误", "起始点不能大于等于结束点！")
            return

        # 3. 读取CSV文件+预处理（核心步骤）
        try:
            # 读取CSV（单列无表头，列名设为voltage）
            df = pd.read_csv(csv_path, header=None, names=['voltage'])
            total_data = len(df)
            print(f"读取到 {total_data} 个电压数据点")

            # 预处理：删除空值、无穷大/无穷小等异常值
            df = df.dropna()  # 去空值
            df = df[np.isfinite(df['voltage'])]  # 去异常值
            valid_data = len(df)
            if valid_data == 0:
                QMessageBox.warning(self, "数据错误", "CSV文件中无有效电压数据（空值/异常值过多）！")
                return
            print(f"预处理后剩余 {valid_data} 个有效数据点")

            # 4. 调整索引区间（避免超出有效数据范围）
            start = start_idx - 1  # 转换为Python 0开始索引
            end = end_idx - 1
            start = max(0, start)  # 最小索引0
            end = min(valid_data - 1, end)  # 最大索引为有效数据-1
            actual_start = start + 1  # 转回1开始的序号（显示用）
            actual_end = end + 1

            # 5. 提取指定区间数据
            filtered_voltage = df.iloc[start:end+1]['voltage'].reset_index(drop=True)
            print(f"筛选区间：第 {actual_start} - 第 {actual_end} 个数据，共 {len(filtered_voltage)} 个点")

        except FileNotFoundError:
            QMessageBox.critical(self, "文件错误", f"未找到CSV文件：{csv_path}")
            return
        except Exception as e:
            QMessageBox.critical(self, "处理错误", f"数据处理失败：{str(e)}")
            return

        # 6. 弹出独立波形窗口并绘图
        self.waveform_window = WaveformWindow(self)  # 父窗口设为主窗口，关闭主窗口时一并关闭
        self.waveform_window.plot(filtered_voltage, actual_start, actual_end)  # 绘制波形
        self.waveform_window.show()  # 显示窗口

    def on_thread_error(self, error_msg):
        """子线程错误处理：关闭空波形窗口，显示错误信息"""
        if hasattr(self, 'waveform_window'):
            self.waveform_window.close()
        self.show_message("错误", error_msg)

    def show_message(self, title, content):
        """通用消息提示（复用你现有程序的提示逻辑也可）"""
        from PyQt5.QtWidgets import QMessageBox
        QMessageBox.information(self, title, content)

    def spectrum_diagram_waveform(self):
        csv_path = self.textEdit_csvPath.toPlainText()
        if not os.path.exists(csv_path):
            QMessageBox.critical(self, "错误", f"文件不存在：\n")
            return False
    
        if not csv_path.lower().endswith(".csv"):
            QMessageBox.critical(self, "错误", f"文件格式错误！\n请选择 .csv 格式文件")
            return False

        try:
            start_frep = int(self.lineEdit_start_frep.text())
            end_frep = int(self.lineEdit_end_frep.text())
            step_frep = int(self.lineEdit_step_frep.text())
            flag_frep = int(self.lineEdit_flag_frep.text())
            gain = float(self.lineEdit_gain1.text())
        except ValueError:
            self.show_message("错误", "频谱图的参数必须是整数！")
            return

        selected_radio = self.radio_group.checkedButton()
        if selected_radio:
            selected_text = selected_radio.text()
            # 执行对应动作
            if selected_text == "无窗":
                window_type = 0
            elif selected_text == "平定窗":
                window_type = 1
            elif selected_text == "汉宁窗":
                window_type = 2
            elif selected_text == "布莱克曼-哈里斯窗":
                window_type = 3

        try:                  
            load_impedance = float(self.lineEdit_impedance.text())                # 负载阻抗（Ω，默认50Ω，射频常用）
            cal_constant = float(self.lineEdit_cal_constant.text()) 
            gain = float(self.lineEdit_gain1.text()) 
        except ValueError:
            self.show_message("错误", "负载阻抗或校准常数或gain有误")
            return

        sample_rate = int(self.lineEdit_sample_rate.text())            # 采样率（Hz，需与原始数据采集时一致，原始代码默认125MHz）

        selected_radio_vpp = self.radio_group_vpp.checkedButton().text()
        selected_radio_dbm = self.radio_group_dbm.checkedButton().text()

        FFT_debug = load_external_file("FFT_debug.py")
        dataDict = FFT_debug.get_fundamental_volt(csv_path, sample_rate, window_type,start_frep,end_frep,step_frep,gain,load_impedance,cal_constant,selected_radio_vpp,selected_radio_dbm)
        self.textBrowser.append('<font color="green">[成功]</font> 频谱数据已经生成')

        # 2. 弹出频谱图窗口并绘制
        self.spectrum_window = WaveformWindow(self)  # 父窗口为主窗口
        self.spectrum_window.plot_spectrum(dataDict,flag_frep)  # 传入频谱数据
        self.spectrum_window.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = aquilaWindow()
    window.show()
    sys.exit(app.exec_())